package de.dhbw.karlsruhe.picsim.model.memory.impl;

import de.dhbw.karlsruhe.picsim.model.cpu.File;
import de.dhbw.karlsruhe.picsim.model.cpu.impl.FileImpl;
import de.dhbw.karlsruhe.picsim.model.io.External;
import de.dhbw.karlsruhe.picsim.model.memory.FileFactory;
import de.dhbw.karlsruhe.picsim.model.memory.HiddenRegisters;
import de.dhbw.karlsruhe.picsim.model.memory.RegisterValue;
import de.dhbw.karlsruhe.picsim.util.Bits;
import de.dhbw.karlsruhe.picsim.util.BitsMutable;
import de.dhbw.karlsruhe.picsim.util.impl.BitUtilities;
import de.dhbw.karlsruhe.picsim.util.impl.BitsMutableImpl;
import de.dhbw.karlsruhe.picsim.util.impl.Lengths;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;

/**
 * Created by IntelliJ IDEA.
 * User: goesen
 * Date: Apr 22, 2010
 * Time: 12:19:52 PM
 */
public class FileFactoryImpl implements FileFactory {
    private static final Log log = LogFactory.getLog(FileFactoryImpl.class);
    private final External external;
    private final File trisB;
    private final File portB;
    private final HiddenRegisters hiddenRegisters;
    private final File trisA;
    private final File portA;


    public FileFactoryImpl(@NotNull final External external, @NotNull final HiddenRegisters hiddenRegisters) {
        this.external = external;
        this.hiddenRegisters = hiddenRegisters;
        trisB = new ExternalReWriteFile();
        portB = new PortB();
        trisA = new ExternalReWriteFile();
        portA = new PortA();
        trisA.setValue(RegisterValueImpl.createUnsafe(31));
        for (int i = 5; i < Lengths.FILE_LENGTH; i++) {
            trisA.makeBitReadOnly(i);
            portA.makeBitReadOnly(i);

        }

        trisB.setValue(RegisterValueImpl.createUnsafe(255));

        external.addListener(new External.ExternalListener() {
            @Override
            public void handlePortAChange(@NotNull Bits oldValue, @NotNull Bits newValue) {
                reValidateExternal();

            }

            @Override
            public void handlePortBChange(@NotNull Bits oldValue, @NotNull Bits newValue) {
                reValidateExternal();

            }
        });


    }

    @Override
    public File createNewFile() {
        return new FileImpl();
    }

    @Override
    public File createUnimplementedFile() {
        final File file = new FileImpl();
        for (int i = 0; i < Lengths.FILE_LENGTH; i++) {
            file.makeBitReadOnly(i);
        }
        return file;
    }

    @Override
    public File getTrisAFile() {
        return trisA;
    }

    @Override
    public File getTrisBFile() {
        return trisB;
    }

    @Override
    public File getPortBFile() {
        return portB;
    }

    private void reValidateExternal() {
        log.debug("revalidation of external");
        final BitsMutable externB = new BitsMutableImpl(external.getPortB());
        final Bits trisBBits = getTrisBFile().getData();

        for (int i = 0; i < Lengths.FILE_LENGTH; i++) {
            if (!trisBBits.isSet(i)) {
                externB.setBit(i, hiddenRegisters.getPortBLatch().isSet(i));
            }
        }
        external.setPortB(externB);

        final BitsMutable externA = new BitsMutableImpl(external.getPortA());
        final Bits trisABits = getTrisAFile().getData();

        for (int i = 0; i < 5; i++) {
            if (!trisABits.isSet(i)) {
                externA.setBit(i, hiddenRegisters.getPortALatch().isSet(i));
            }
        }
        external.setPortA(externA);


    }

    @Override
    public File getPortAFile() {
        return portA;
    }

    private final class PortB implements File {
        private final File delegate;

        private PortB() {
            delegate = new FileImpl();


        }

        public void setValue(@NotNull final RegisterValue value) {
            hiddenRegisters.setPortBLatch(value);
            reValidateExternal();
        }

        @Override
        public void makeBitReadOnly(final int which) {
            delegate.makeBitReadOnly(which);
        }

        public void makeBitWriteAble(final int which) {
            delegate.makeBitWriteAble(which);
        }

        @Override
        public RegisterValue getData() {
            return new RegisterValueImpl(external.getPortB());
        }

    }

    private final class PortA implements File {
        private final File delegate;

        private PortA() {
            delegate = new FileImpl();
        }

        public void setValue(@NotNull final RegisterValue value) {
            hiddenRegisters.setPortALatch(BitUtilities.subBits(value, 0, 5));
            reValidateExternal();
        }

        @Override
        public void makeBitReadOnly(final int which) {
            delegate.makeBitReadOnly(which);
        }

        public void makeBitWriteAble(final int which) {
            delegate.makeBitWriteAble(which);
        }

        @Override
        public RegisterValue getData() {
            final Bits portABits = external.getPortA();
            final Bits unimplemented = BitUtilities.asLeastSignificantBitFirst(0, 3);
            return new RegisterValueImpl(BitUtilities.concat(portABits, unimplemented));
        }

    }


    private final class ExternalReWriteFile implements File {
        private final File delegate;

        private ExternalReWriteFile() {
            delegate = new FileImpl();
        }

        @Override
        public void setValue(@NotNull final RegisterValue value) {
            final Bits oldData = getData();
            delegate.setValue(value);
            if (!value.equals(oldData)) {
                reValidateExternal();
            }
        }

        @Override
        public void makeBitReadOnly(final int which) {
            delegate.makeBitReadOnly(which);
        }

        @Override
        public void makeBitWriteAble(final int which) {
            delegate.makeBitWriteAble(which);
        }

        @Override
        public RegisterValue getData() {
            return delegate.getData();
        }
    }
}
